#include "md5_mesh_parser.h"

using namespace krryn::data;

md5_mesh_parser::md5_mesh_parser(const std::string &a_Filename, size_t a_MemoryBudgetBytes)
	: md5_parser(a_Filename, a_MemoryBudgetBytes) {}

void md5_mesh_parser::parse(){
	try{
		header();
		settings();
		joints_block();
		mesh_block();
	}catch(const md5_lexer_eof &){
		if(!is_valid_eof()){
			error("unexpected end of file");
		}
	}catch(const md5_token_exception &a_Token){
		// token exceptions happen when extracting data from an incorrect token,
		// turn them into syntax error.
		unexpected(a_Token.m_ExpectedTag, a_Token.m_CurrentTag);
	}catch(const md5_parse_error &a_ParseError){
		throw a_ParseError;
	}catch(...){
		error("fatal error");
	}
}

void md5_mesh_parser::settings(){
	int l_Count = 0;

	switch(m_Look.m_Tag){
		case num_joints:
			match(num_joints);

			if(m_Model.m_NumJoints != -1){
				unexpected(num_joints);
			}

			l_Count = m_Look.as_int();

			request_allocation(sizeof(md5_joint) * l_Count);

			m_Model.m_NumJoints   = l_Count;
			m_Model.m_BindingPose = new md5_joint[l_Count];
			
			match(integer);
			settings();
			break;
		case num_meshes:
			match(num_meshes);

			if(m_Model.m_NumMeshes != -1){
				unexpected(num_meshes);
			}

			l_Count = m_Look.as_int();

			request_allocation(sizeof(md5_mesh) * l_Count);

			m_Model.m_NumMeshes = l_Count;
			m_Model.m_Meshes    = new md5_mesh[l_Count];

			match(integer);
			settings();
			break;
	}
}

void md5_mesh_parser::joints_block(){
	match(joints); match(open_brace);

	for(int i = 0; i < m_Model.m_NumJoints; i++){
		m_Model.m_BindingPose[i] = joint();
	}

	match(close_brace);
}

md5_joint md5_mesh_parser::joint(){
	md5_joint l_Joint;
	l_Joint.m_Name = m_Look.as_string();

	match(string);

	l_Joint.m_Parent = m_Look.as_int();
	match(integer);

	l_Joint.m_Position = vector();
	l_Joint.m_Rotation = quat();

	return l_Joint;
}

void md5_mesh_parser::mesh_block(){
	for(int i = 0; i < m_Model.m_NumMeshes; i++){
		match(mesh);
		match(open_brace);

		mesh_data(i);

		match(close_brace);
	}
}

void md5_mesh_parser::mesh_data(int a_Index){
	int l_Count = -1;
	switch(m_Look.m_Tag){
		case numverts:
			match(numverts);

			if(m_Model.m_Meshes[a_Index].m_NumVertices != -1){
				unexpected(numverts); 
			}

			l_Count = m_Look.as_int();

			request_allocation(sizeof(md5_vertex) * l_Count);

			m_Model.m_Meshes[a_Index].m_NumVertices = l_Count;
			m_Model.m_Meshes[a_Index].m_Vertices = new md5_vertex[l_Count];

			match(integer);
			mesh_data(a_Index); 
			break;
		case numtris:
			match(numtris);

			if(m_Model.m_Meshes[a_Index].m_NumTriangles != -1){
				unexpected(numtris);
			}

			l_Count = m_Look.as_int();

			request_allocation(sizeof(md5_triangle) * l_Count);

			m_Model.m_Meshes[a_Index].m_NumTriangles = l_Count;
			m_Model.m_Meshes[a_Index].m_Triangles = new md5_triangle[l_Count];

			match(integer);
			mesh_data(a_Index);
			break;
		case numweights:
			match(numweights);

			if(m_Model.m_Meshes[a_Index].m_NumWeights != -1){
				unexpected(numweights);
			}

			l_Count = m_Look.as_int();

			request_allocation(sizeof(md5_weight) * l_Count);

			m_Model.m_Meshes[a_Index].m_NumWeights = l_Count;
			m_Model.m_Meshes[a_Index].m_Weights = new md5_weight[l_Count];

			match(integer);
			mesh_data(a_Index);
			break;
		case shader:
			shader_data(a_Index);
			break;
		case vert:
			vertex(a_Index);
			break;
		case tri:
			triangle(a_Index);
			break;
		case weight:
			weight_data(a_Index);
			break;
	}
}

void md5_mesh_parser::shader_data(int a_Index){
	match(shader);

	m_Model.m_Meshes[a_Index].m_Shader = m_Look.as_string();

	match(string);
	mesh_data(a_Index);
}

void md5_mesh_parser::vertex(int a_Index){
	int l_Count = m_Model.m_Meshes[a_Index].m_NumVertices;

	if(l_Count == -1){
		unexpected(numverts, m_Look.m_Tag);
	}

	for(int i = 0; i < l_Count; i++){
		match(vert);

		int l_Index = m_Look.as_int();
		match(integer);

		md5_vertex &l_Vertex = m_Model.m_Meshes[a_Index].m_Vertices[l_Index];

		match(open_set);
		l_Vertex.m_UV.m_U = m_Look.as_float();
		match(real);

		l_Vertex.m_UV.m_V = m_Look.as_float();
		match(real);
		match(close_set);

		l_Vertex.m_WeightStart = m_Look.as_int();
		match(integer);

		l_Vertex.m_WeightCount = m_Look.as_int();
		match(integer);
	}

	mesh_data(a_Index);
}

void md5_mesh_parser::triangle(int a_Index){
	int l_Count = m_Model.m_Meshes[a_Index].m_NumTriangles;

	if(l_Count == -1){
		unexpected(numtris, m_Look.m_Tag);
	}

	for(int i = 0; i < l_Count; i++){
		match(tri);

		int l_Index = m_Look.as_int();
		match(integer);

		md5_triangle &l_Triangle = m_Model.m_Meshes[a_Index].m_Triangles[l_Index];

		for(int i = 0; i < 3; i++){
			l_Triangle.m_Vertices[i] = m_Look.as_int();
			match(integer);
		}
	}

	mesh_data(a_Index);
}

void md5_mesh_parser::weight_data(int a_Index){
	int l_Count = m_Model.m_Meshes[a_Index].m_NumWeights;

	if(l_Count == -1){
		unexpected(numweights, m_Look.m_Tag);
	}

	for(int i = 0; i < l_Count; i++){
		match(weight);
		
		int l_Index = m_Look.as_int();
		match(integer);

		md5_weight &l_Weight = m_Model.m_Meshes[a_Index].m_Weights[l_Index];

		l_Weight.m_Joint = m_Look.as_int();
		match(integer);

		l_Weight.m_Bias = m_Look.as_float();
		match(real);

		l_Weight.m_Position = vector();
	}

	mesh_data(a_Index);
}

bool md5_mesh_parser::is_valid_eof(){
	/// validate the entire model when we're done parsing, just to check
	/// if someone didn't try to give us invalid data.
	try{
		m_Model.validate();
	}catch(const md5_node_exception &a_Exception){
		throw md5_parse_error(
			std::string("Invalid end of file: ") + a_Exception.what());
	}

	return m_Look.m_Tag == close_brace;
}
